/*
 * Copyright 2010 Alessio Stalla.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package dynaspring;

import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.springframework.util.PropertyPlaceholderHelper;

/**
 * Extension of Properties that allows to define list and map valued properties.
 * @author Alessio Stalla
 *
 */
public class SuperProperties extends Properties implements ISuperProperties {

	private static final long serialVersionUID = 1L;
	protected Properties parent;
	
	/** Prefix for system property placeholders: "${" */
	public static final String PLACEHOLDER_PREFIX = "${";

	/** Suffix for system property placeholders: "}" */
	public static final String PLACEHOLDER_SUFFIX = "}";
	
	/** Value separator for system property placeholders: ":" */
	public static final String VALUE_SEPARATOR = ":";
	
	public SuperProperties() { super(); }

	public SuperProperties(Properties p) {
		super(p);
		this.parent = p;
	}
	
	/**
	 * Returns the parent Properties object.
	 * @return
	 */
	public Properties getParent() {
		return parent;
	}
	
	public String replacePlaceholders(String value) {
		return replacePlaceholders(value, true);
	}
	
	public String replacePlaceholders(String value, boolean ignoreUnresolvablePlaceholders) {
		return new PropertyPlaceholderHelper(PLACEHOLDER_PREFIX, PLACEHOLDER_SUFFIX, VALUE_SEPARATOR, ignoreUnresolvablePlaceholders).replacePlaceholders(value, this);
	}

	/**
	 * Returns the raw value of the given property, without interpreting it in any way.
	 */
	public String getRawProperty(String name, String defaultValue) {
		String prop = (String) get(name);
		if(prop != null) {
			return prop;
		} else if(parent != null) {
			if(parent instanceof ISuperProperties) {
				return ((ISuperProperties) parent).getRawProperty(name, defaultValue);
			} else {
				return parent.getProperty(name, defaultValue);
			}
		} else {
			return defaultValue;
		}
	}
	
	@Override
	public String getProperty(String name) {
		String prop = getRawProperty(name, null);
		return processProperty(prop, null);
	}
	
	@Override
	public String getProperty(String name, String defaultValue) {
		String prop = getRawProperty(name, defaultValue);
		return processProperty(prop, defaultValue);
	}

	protected String processProperty(String prop, String defaultValue) {
		if(prop != defaultValue) {
			prop = replacePlaceholders(prop);
			return prop;
		} else {
			return prop;
		}
	}
	
	@Override
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public List getList(String name) {
		String listClass = getProperty(name + ".type", "java.util.ArrayList");
		try {
			List list = (List) Class.forName(listClass).newInstance();
			for(int i = 0; true; i++) {
				String val = getProperty(name + "[" + i + "]");
				if(val != null) {
					list.add(val);
				} else {
					break;
				}
			}
			return list;
		} catch (Exception e) {
			throw new RuntimeException("Cannot instantiate list " + name + " of class " + listClass, e);
		}
	}

	@Override
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public Map getMap(String name) {
		String mapClass = getProperty(name + ".type", "java.util.HashMap");
		try {
			Map map = (Map) Class.forName(mapClass).newInstance();
			for(Enumeration<?> en = this.propertyNames(); en.hasMoreElements();) {
				String nextProp = en.nextElement().toString();
				if(nextProp.startsWith(name + "[") && nextProp.endsWith("]")) {
					String key = nextProp.substring(name.length() + 1, nextProp.length() - 1);
					map.put(replacePlaceholders(key), getProperty(nextProp));
				}
			}
			return map;
		} catch (Exception e) {
			throw new RuntimeException("Cannot instantiate map " + name + " of class " + mapClass, e);
		}
	}
		
}
